一、相关子查询:父查询换一行,子查询跟着重算
核心思想:代入法——把父查询当前行的值代进子查询
定义
子查询的 WHERE 里用到了父查询当前行的字段(比如 s.sno),这个子查询就叫相关子查询。
"相关"的意思:子查询的结果依赖外层的当前行。
📝 任务:查询选修了课程 c1 的学号和姓名
SELECT sno, sn
FROM s
WHERE 'c1' IN (
SELECT cno
FROM sc
WHERE sno = s.sno -- 这里的 s.sno 会被"代入"成具体学号
);🎬 动态演示 点学号或按「下一步」
当前处理学生:
父查询学生表 s(当前看的这一行)
外层判断这一行要不要进结果集
代入后子查询实际执行的是
扫描中选课表 sc(全表)
结果子查询返回的课程号集合
📦 已累积的结果集(处理到当前行为止)
💬 一句话总结
上面动画里,黑底代码每换一次学生,s.sno 就变成一个新的具体学号。这就是"相关子查询"四个字的全部含义——子查询的结果和父查询当前行紧密相关。
二、EXISTS:不看返回什么,只看"有没有"
核心思想:点名册——我只问"你来没来",不问"你考了几分"
EXISTS 问的是不同的问题
前面所有子查询都在关心"返回什么值":课程号是啥?分数多少?
EXISTS 问的是更简单的问题:子查询结果里有没有记录?
有记录 → TRUE | 没记录 → FALSE | 记录里是什么,完全不关心
IN
你考了 几分?课程号是 哪个?
——要读取具体的值
——要读取具体的值
EXISTS
你来了吗?点名册上有这号人吗?
——只看有没有
——只看有没有
正因为只看"有没有",子查询里 SELECT 什么都一样
SELECT *✓ 常用
SELECT 1✓ 相同
SELECT 'x'✓ 相同
SELECT sno✓ 相同
EXISTS 根本不读这些值,它只数返回了几行。习惯上写 SELECT *,表示"随便选"。
📝 同一个任务:查询选修了课程 c1 的学号和姓名
现在换成 EXISTS 写。注意看 WHERE 里条件怎么变的。
SELECT sno, sn
FROM s
WHERE EXISTS (
SELECT *
FROM sc
WHERE sno = s.sno AND cno = 'c1' -- 把条件收紧到"这个学生且选了c1"
);🎬 EXISTS 动态演示 只看有没有
当前处理学生:
父查询学生表 s(当前看的这一行)
外层判断这一行要不要进结果集
代入后子查询实际执行的是
扫描中选课表 sc(全表)
核心问题子查询有没有返回记录
EXISTS 只问一件事:上面有返回行吗?
📦 已累积的结果集(处理到当前行为止)
🥊 IN 和 EXISTS,思维方向完全相反
用 IN 的思路
"'c1' 这个值,在不在子查询返回的那堆课程号里面?"
- 子查询要真的读取 cno 的值
- 返回一个集合,比如 {c1, c2}
- 再检查 'c1' 是不是这个集合里的一员
- 方向:值 → 集合
用 EXISTS 的思路
"对这个学生,能不能找到一条 cno='c1' 的记录?"
- 子查询把条件收紧到 cno='c1'
- 只要能查到至少一行,就为真
- 根本不读值,只看行数是不是 > 0
- 方向:条件 → 存在性
🧠 两节课一句话收尾
相关子查询 = 把父查询的值代入子查询,逐行重算;EXISTS = 只问子查询"有没有",不问"是什么"。
两者是天然搭档:EXISTS 必须要父查询当前行的值来"试探",所以它几乎总是跟相关子查询一起用。